/*
 * The Yard Utilties - http://www.theyard.net/
 * 
 * Copyright (c) 2008 by Vlideshow, Inc..  All Rights Resrved.
 * 
 * This library is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License as published by the Free Software 
 * Foundation; either version 2.1 of the License, or (at your option) any later 
 * version. 
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along 
 * with this library; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */
package net.theyard.components.sm
{
  import net.theyard.components.Utils;

  /**
   * A class that can help you more easily write tests for state machines.
   *
   * This class provides a framework that is useful for
   * testing state machines.  It allows you to set
   * expected next states, and calls ASUnit fails
   * when states are not hit.
   *
   */
  public class StateMachineTestHelper
  {
    private var nextStates:Object;
    public function StateMachineTestHelper()
    {
      super();
    }

    /*
     * Sets the next states the helper expects to get.
     *
     * You can either pass in a State name (string) directly, or you can
     * pass in an object that maps state names to booleans.
     *
     * For example:
     * <pre>
     *  setNextStaes("STATE_NAME1", "STATE_NAME2");
     * </pre>
     * or
     * <pre>
     *  setNextStates({ "STATE_NAME1" : true, "STATE_NAME2": true});
     * </pre>
     * @param arguments Either a series of strings (state names) or an associate array.
     * @see #checkState()
     */
    public function setNextStates(...arguments) : void
    {
      this.nextStates = {};
      if (arguments != null)
      {
        for (var i:int = 0; i < arguments.length; i++)
        {
          if (arguments[i] is String)
          {
            var state:String = String(arguments[i]);
            this.nextStates[state] = true;
          }
          else
          {
            this.nextStates = arguments[i];
            if (arguments.length != 1)
            {
              // Hmm... not sure person understands how to pass in here.
              throw new ArgumentError(
                  "Either pass in Strings or a single Associated Array object");
            }
          }
        }
      }
    }

    /**
     * Checks the given state is one of the expected next states.
     *
     * @param state State to check
     * @see #setNextStates()
     */
    public function checkState(state:String) : void
    {
      var validState:Boolean = false;

      try
      {
        if (this.nextStates != null &&
            this.nextStates[state] != null)
        {
          try
          {
            validState = Boolean(this.nextStates[state]);
          }
          catch (ex : Error)
          {
            // The value in the array is not a boolean; let's
            // not punish people for that.  if they've
            // set something here, let's make it only be false
            // to disable things.
            validState = true;
          }
        }
      }
      catch ( ex : Error)
      {
        // This should trip if the state isn't in the Object.
        validState = false;
      }

      if (!validState)
      {
        var errorMsg:String = "";

        errorMsg = "Unexpected state transition: Got [" + state +"]; Expected [";
        if (this.nextStates != null)
        {
          for (var expState:String in this.nextStates)
          {
            errorMsg = errorMsg + expState + " : ";
          }
        }
        errorMsg = errorMsg + "]";
        Utils.ytrace(errorMsg);
        throw new Error(errorMsg);
      }
    }

    /**
     * Checks the current state and sets the next states.
     *
     * @param state The state to check
     * @param arguments The arguments to setNextStates
     *
     * @see #checkState()
     * @see #setNextStates()
     */
    public function checkStateAndSetNextStates(state:String, ... arguments):void
    {
      this.checkState(state);
      this.setNextStates.apply(this, arguments);
    }

  }
}
